﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

using System.Linq;
using System.Linq.Expressions;

namespace RaisingStudio.Data.Linq
{
    public class CommandBuilder : ExpressionVisitor
    {
        public static CommonCommand GetCommand(System.Linq.Expressions.Expression expression)
        {
            return (new CommandBuilder(expression)).GetCommand();
        }

        public static CommonCommand GetCommand(System.Linq.Expressions.Expression expression, out RaisingStudio.Data.Expressions.ColumnExpression[] columns)
        {
            return (new CommandBuilder(expression)).GetCommand(out columns);
        }

        public static CommonCommand GetCommand(System.Linq.Expressions.Expression expression, out string[] columns)
        {
            return (new CommandBuilder(expression)).GetCommand(out columns);
        }

        public static RaisingStudio.Data.Expressions.ColumnExpression[] GetColumnExpressions(Expression expression)
        {
            if ((expression is UnaryExpression) && (expression.NodeType == ExpressionType.Quote))
            {
                expression = (expression as UnaryExpression).Operand;
            }
            if ((expression is LambdaExpression) && (expression.NodeType == ExpressionType.Lambda))
            {
                LambdaExpression l = expression as LambdaExpression;
                Expression e = l.Body;
                if (e.NodeType == ExpressionType.Convert)
                {
                    e = (e as UnaryExpression).Operand;
                }
                if ((e is MemberExpression) && (e.NodeType == ExpressionType.MemberAccess))
                {
                    MemberExpression m = e as MemberExpression;
                    return new RaisingStudio.Data.Expressions.ColumnExpression[] { new RaisingStudio.Data.Expressions.ColumnExpression(m.Member.Name) };
                }
                else if ((e is NewExpression) && (e.NodeType == ExpressionType.New))
                {
                    NewExpression n = e as NewExpression;
                    List<RaisingStudio.Data.Expressions.ColumnExpression> columnExpressionList = new List<RaisingStudio.Data.Expressions.ColumnExpression>();
                    foreach (var a in n.Arguments)
                    {
                        if ((a is MemberExpression) && (a.NodeType == ExpressionType.MemberAccess))
                        {
                            MemberExpression m = a as MemberExpression;
                            columnExpressionList.Add(new RaisingStudio.Data.Expressions.ColumnExpression(m.Member.Name));
                        }
                    }
                    return columnExpressionList.ToArray();
                }
                else if ((e is NewArrayExpression) && (e.NodeType == ExpressionType.NewArrayInit))
                {
                    NewArrayExpression n = e as NewArrayExpression;
                    List<RaisingStudio.Data.Expressions.ColumnExpression> columnExpressionList = new List<RaisingStudio.Data.Expressions.ColumnExpression>();
                    foreach (var a in n.Expressions)
                    {
                        if ((a is MemberExpression) && (a.NodeType == ExpressionType.MemberAccess))
                        {
                            MemberExpression m = a as MemberExpression;
                            columnExpressionList.Add(new RaisingStudio.Data.Expressions.ColumnExpression(m.Member.Name));
                        }
                    }
                    return columnExpressionList.ToArray();
                }
            }
            return null;
        }

        public static RaisingStudio.Data.Expressions.ColumnExpression GetColumnExpression(Expression expression)
        {
            if ((expression is UnaryExpression) && (expression.NodeType == ExpressionType.Quote))
            {
                expression = (expression as UnaryExpression).Operand;
            }
            if ((expression is LambdaExpression) && (expression.NodeType == ExpressionType.Lambda))
            {
                LambdaExpression l = expression as LambdaExpression;
                Expression e = l.Body;
                if (e.NodeType == ExpressionType.Convert)
                {
                    e = (e as UnaryExpression).Operand;
                }
                if ((e is MemberExpression) && (e.NodeType == ExpressionType.MemberAccess))
                {
                    MemberExpression m = e as MemberExpression;
                    return new RaisingStudio.Data.Expressions.ColumnExpression(m.Member.Name);
                }
                else if ((e is NewExpression) && (e.NodeType == ExpressionType.New))
                {
                    NewExpression n = e as NewExpression;
                    foreach (var a in n.Arguments)
                    {
                        if ((a is MemberExpression) && (a.NodeType == ExpressionType.MemberAccess))
                        {
                            MemberExpression m = a as MemberExpression;
                            return new RaisingStudio.Data.Expressions.ColumnExpression(m.Member.Name);
                        }
                    }
                }
            }
            return null;
        }

        public static string[] GetColumnNames(RaisingStudio.Data.Expressions.ColumnExpression[] columns)
        {
            if (columns != null)
            {
                string[] columnNames = new string[columns.Length];
                for (int i = 0; i < columns.Length; i++)
                {
                    columnNames[i] = (string)columns[i];
                }
                return columnNames;
            }
            return null;
        }


        protected System.Linq.Expressions.Expression expression;
        public System.Linq.Expressions.Expression Expression
        {
            get { return expression; }
            private set { expression = value; }
        }

        public CommandBuilder(System.Linq.Expressions.Expression expression)
        {
            this.expression = expression;
        }

        /// <summary>
        /// Format string of parameter name.
        /// </summary>
        public const string PARAMETERNAMEFORMAT = "@pe{0}";

        public virtual CommonCommand GetCommand()
        {
            if (this.expression != null)
            {
                if (this.command == null)
                {
                    Build();
                }
                return this.command;
            }
            else
            {
                return null;
            }
        }

        public virtual CommonCommand GetCommand(out RaisingStudio.Data.Expressions.ColumnExpression[] columns)
        {
            columns = null;
            if (this.expression != null)
            {
                if (this.command == null)
                {
                    Build();
                }
                columns = this.columnExpressions.ToArray();
                return this.command;
            }
            else
            {
                return null;
            }
        }

        public virtual CommonCommand GetCommand(out string[] columnNames)
        {
            RaisingStudio.Data.Expressions.ColumnExpression[] columns;
            CommonCommand command = GetCommand(out columns);
            columnNames = GetColumnNames(columns);
            return command;
        }


        protected int parameterIndex = 0;
        protected CommonCommand command;
        protected Stack<CommonCommand> commandStack = new Stack<CommonCommand>();
        protected Stack<RaisingStudio.Data.Expressions.ColumnExpression> columnStack = new Stack<RaisingStudio.Data.Expressions.ColumnExpression>();
        protected List<RaisingStudio.Data.Expressions.SortingExpression> sortingExpressions = new List<RaisingStudio.Data.Expressions.SortingExpression>();
        protected List<RaisingStudio.Data.Expressions.ColumnExpression> columnExpressions = new List<RaisingStudio.Data.Expressions.ColumnExpression>();

        public virtual void Build()
        {
            this.commandStack.Clear();
            this.sortingExpressions.Clear();
            this.columnExpressions.Clear();
            this.parameterIndex = 0;

            ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator();
            Expression evaluatedExpression = expressionEvaluator.Evaluate(expression);

            this.Visit(evaluatedExpression);

            CommonCommand condition = this.commandStack.Count > 0 ? this.commandStack.Pop() : null;
            // why that?
            if ((condition != null) && (condition.Parameters != null) && (condition.Parameters.Count == 1) && (condition.CommandText == condition.Parameters[0].ParameterName) && this.commandStack.Count == 0)
            {
                condition = null;
            }
            CommonCommand command = new CommonCommand();
            string commandText = string.Empty;
            if ((condition != null) && (!string.IsNullOrEmpty(condition.CommandText)))
            {
                commandText = string.Format("WHERE {0}", condition.CommandText);
                foreach (var parameter in condition.Parameters)
                {
                    command.Parameters.Add(parameter);
                }
            }
            StringBuilder sortingExpressionStringBuilder = new StringBuilder();
            if (this.sortingExpressions.Count > 0)
            {
                sortingExpressionStringBuilder.Append("ORDER BY ");

                bool sortingExpressionStringSplitted = false;
                foreach (RaisingStudio.Data.Expressions.SortingExpression sortingExpression in this.sortingExpressions)
                {
                    string sortingExpressionString = RaisingStudio.Data.Common.Builders.Expressions.ConditionExpressionCommandBuilder.GetSortingExpressionString(sortingExpression);
                    if (sortingExpressionStringSplitted)
                    {
                        sortingExpressionStringBuilder.Append(", ");
                    }
                    sortingExpressionStringBuilder.Append(sortingExpressionString);
                    sortingExpressionStringSplitted = true;
                }

                if (!string.IsNullOrEmpty(commandText))
                {
                    commandText = string.Format("{0} {1}", commandText, sortingExpressionStringBuilder);
                }
                else
                {
                    commandText = sortingExpressionStringBuilder.ToString();
                }
            }
            command.CommandText = commandText;

            this.command = command;
        }

        protected override Expression VisitBinary(BinaryExpression b)
        {
            if (b != null)
            {
                string operratorString;
                switch (b.NodeType)
                {
                    case ExpressionType.Equal:
                        {
                            operratorString = "=";
                            break;
                        }
                    case ExpressionType.NotEqual:
                        {
                            operratorString = "<>";
                            break;
                        }
                    case ExpressionType.GreaterThan:
                        {
                            operratorString = ">";
                            break;
                        }
                    case ExpressionType.GreaterThanOrEqual:
                        {
                            operratorString = ">=";
                            break;
                        }
                    case ExpressionType.LessThan:
                        {
                            operratorString = "<";
                            break;
                        }
                    case ExpressionType.LessThanOrEqual:
                        {
                            operratorString = "<=";
                            break;
                        }

                    case ExpressionType.AndAlso:
                        {
                            operratorString = "AND";
                            break;
                        }
                    case ExpressionType.OrElse:
                        {
                            operratorString = "OR";
                            break;
                        }

                    case ExpressionType.Add:
                        {
                            operratorString = "+";
                            break;
                        }
                    case ExpressionType.Subtract:
                        {
                            operratorString = "-";
                            break;
                        }
                    case ExpressionType.Multiply:
                        {
                            operratorString = "*";
                            break;
                        }
                    case ExpressionType.Divide:
                        {
                            operratorString = "/";
                            break;
                        }
                    case ExpressionType.Modulo:
                        {
                            operratorString = "%";
                            break;
                        }

                    default:
                        {
                            throw new NotSupportedException(b.NodeType + " is not supported.");
                        }
                }

                this.Visit(b.Left);
                this.Visit(b.Right);

                CommonCommand right = this.commandStack.Pop();
                CommonCommand left = this.commandStack.Pop();

                string commandText = String.Format("({0} {1} {2})", left, operratorString, right);
                CommonCommand command = new CommonCommand(commandText);
                foreach (var parameter in left.Parameters)
                {
                    command.Parameters.Add(parameter);
                }
                foreach (var parameter in right.Parameters)
                {
                    command.Parameters.Add(parameter);
                }

                this.commandStack.Push(command);
            }
            return b;
        }

        protected override Expression VisitConstant(ConstantExpression c)
        {
            if (c != null)
            {
                string parameterName = string.Format(PARAMETERNAMEFORMAT, this.parameterIndex++);
                CommonCommand command = new CommonCommand(parameterName);
                command.Parameters.Add(parameterName, c.Value);
                this.commandStack.Push(command);
            }
            return c;
        }

        protected override Expression VisitMemberAccess(MemberExpression m)
        {
            if (m != null)
            {
                PropertyInfo propertyInfo = m.Member as PropertyInfo;
                if (propertyInfo != null)
                {
                    this.commandStack.Push(new CommonCommand(string.Format("[{0}]", propertyInfo.Name)));
                    this.columnStack.Push(new RaisingStudio.Data.Expressions.ColumnExpression(propertyInfo.Name));
                }
            }
            return m;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m != null)
            {
                MethodInfo methoInfo = m.Method;
                if (methoInfo != null)
                {
                    switch (methoInfo.Name)
                    {
                        case "OrderBy":
                        case "ThenBy":
                            {
                                if ((m.Arguments != null) && (m.Arguments.Count > 1))
                                {
                                    this.Visit(m.Arguments[0]);
                                    this.Visit(m.Arguments[1]);
                                    {
                                        if (this.columnStack.Count > 0)
                                        {
                                            RaisingStudio.Data.Expressions.ColumnExpression columnExpression = this.columnStack.Pop();
                                            this.sortingExpressions.Add(new RaisingStudio.Data.Expressions.SortingExpression(columnExpression));
                                            this.commandStack.Pop();
                                        }
                                    }
                                }
                                return m;
                            }
                        case "OrderByDescending":
                        case "ThenByDescending":
                            {
                                if ((m.Arguments != null) && (m.Arguments.Count > 1))
                                {
                                    this.Visit(m.Arguments[0]);
                                    this.Visit(m.Arguments[1]);
                                    {
                                        if (this.columnStack.Count > 0)
                                        {
                                            RaisingStudio.Data.Expressions.ColumnExpression columnExpression = this.columnStack.Pop();
                                            this.sortingExpressions.Add(new RaisingStudio.Data.Expressions.SortingExpression(columnExpression, RaisingStudio.Data.Expressions.SortingDirection.Descending));
                                            this.commandStack.Pop();
                                        }
                                    }
                                }
                                return m;
                            }
                        case "Select":
                            {
                                if ((m.Arguments != null) && (m.Arguments.Count > 1))
                                {
                                    this.Visit(m.Arguments[0]);
                                    RaisingStudio.Data.Expressions.ColumnExpression[] columns = GetColumnExpressions(m.Arguments[1]);
                                    if (columns != null)
                                    {
                                        this.columnExpressions.AddRange(columns);
                                    }
                                }
                                return m;
                            }
                    }
                }
            }
            // TODO: call method.
            return base.VisitMethodCall(m);
        }
    }
}
